package contract

import (
	"encoding/json"
	"fmt"
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"github.com/hyperledger/fabric/protos/peer"
	"reflect"
)

// participant: organization member of fabric consortium
type Participant interface {
	MemberID() string
}

type Asset interface {
}

type Contract struct {
	Version string 
	Participants map[string]Participant
	Asset  map[string]Asset
}

type Request struct {
	AssetName string  `json:"assetName"`
	FcnName  string  `json:"fcnName"`
	Args map[string]interface{} `json:"args"`
}

func (req Request) methodName() string {
	return req.AssetName + "." + req.FcnName
}

type Response struct {
	State  int32
	Message string
	Payload []byte
}

func (rsp Response) response() peer.Response {
	return peer.Response{
		Status:  rsp.State,
		Message: rsp.Message,
		Payload: rsp.Payload,
	}
}

type assetObject struct {
	methods  map[string]reflect.Method
	value reflect.Value
}

type smartContract struct {
	Contract
	methods  map[string]reflect.Method
	assetObjects map[string]*assetObject
}

func (sc *smartContract) Init(stub shim.ChaincodeStubInterface) peer.Response {
	//args := stub.GetArgs()
	fmt.Println("init chaincode")
	sc.assetObjects = make(map[string]*assetObject)
	for name, asset := range sc.Asset {
		fmt.Println("asset: ", name)
		typ := reflect.TypeOf(asset)
		methodNum := typ.NumMethod()
		assetObj := &assetObject{}
		assetObj.methods = make(map[string]reflect.Method)
		assetObj.value = reflect.ValueOf(asset)
		for i := 0; i < methodNum; i++ {
			method := typ.Method(i)
			numIn := method.Type.NumIn()
			numOut := method.Type.NumOut()
			fmt.Println(numIn, numOut)
			if numIn != 2 || numOut != 1 {
				continue
			}
			if method.Type.Out(0).Kind() != reflect.Struct {
				continue
			}
			assetObj.methods[method.Name] = method
		}
		sc.assetObjects[name] = assetObj
	}
	return shim.Success(nil)
}

func (sc *smartContract) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
	args := stub.GetArgs()
	var req Request
	err := json.Unmarshal(args[0], &req)
	if err != nil {
		return shim.Error("invalid input: "+ err.Error())
	}

	assetObj, _ := sc.assetObjects[req.AssetName]
	if assetObj == nil {
		return shim.Error("not found asset " + req.AssetName)
	}
	method, ok := assetObj.methods[req.FcnName]
	if !ok {
		return shim.Error("not found method " + req.FcnName)
	}

	fmt.Println("type: ", assetObj.value.Type())
	ctx, err := initContext(stub, sc.Contract, req.Args)
	if err != nil {
		return shim.Error(err.Error())
	}
	fmt.Println("type: ",method.Type, "name:", method.Name, "pkgPath:", method.PkgPath)
	fmt.Println(assetObj.value.Type(), reflect.ValueOf(ctx).Type())
	resp := method.Func.Call([]reflect.Value{assetObj.value, reflect.ValueOf(ctx)})
	if len(resp) != 1 {
		return shim.Error("call error")
	}
	reply := resp[0].Interface().(Response)
	return reply.response()
}

func (c Contract) Deploy() error {
	sc := &smartContract{Contract:c}
	if err := shim.Start(sc); err != nil {
		return fmt.Errorf("chaincode start fail: %v", err)
	}
	return nil
}

